package com.dbflow5.processor.definition;

import com.dbflow5.processor.ClassNames;
import com.dbflow5.processor.Handlers;
import com.dbflow5.processor.ProcessorManager;
import com.dbflow5.processor.utils.ElementExtensions;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import javax.lang.model.element.Modifier;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Description: Top-level writer that handles writing all [DatabaseDefinition]
 * and [com.dbflow5.annotation.TypeConverter]
 */
public class DatabaseHolderDefinition implements TypeDefinition {
    public static final String OPTION_TARGET_MODULE_NAME = "targetModuleName";

    private final ProcessorManager processorManager;

    public String className;

    public DatabaseHolderDefinition(ProcessorManager processorManager) {
        this.processorManager = processorManager;

        String _className = "";
        Map<String, String> options = this.processorManager.processingEnvironment.getOptions();
        if (options.containsKey(OPTION_TARGET_MODULE_NAME)) {
            _className = options.get(OPTION_TARGET_MODULE_NAME) != null? options.get(OPTION_TARGET_MODULE_NAME) : "";
        }

        _className += ClassNames.DATABASE_HOLDER_STATIC_CLASS_NAME;

        className = _className;
    }

    @Override
    public TypeSpec typeSpec() {
        TypeSpec.Builder builder = TypeSpec.classBuilder(this.className);
        builder.addModifiers(Modifier.PUBLIC, Modifier.FINAL);
        builder.superclass(ClassNames.DATABASE_HOLDER);

        MethodSpec.Builder methodBuilder = MethodSpec.constructorBuilder();
        methodBuilder.addModifiers(Modifier.PUBLIC);

        processorManager.getTypeConverters().forEach(tc -> {
            methodBuilder.addStatement("$L.put($T.class, new $T())",
                    Handlers.DatabaseHandler.TYPE_CONVERTER_MAP_FIELD_NAME,
                    ElementExtensions.rawTypeName(tc.modelTypeName),
                    tc.className);

            tc.allowedSubTypes.forEach(subType -> {
                methodBuilder.addStatement("$L.put($T.class, new $T())",
                        Handlers.DatabaseHandler.TYPE_CONVERTER_MAP_FIELD_NAME,
                        ElementExtensions.rawTypeName(subType), tc.className);
            });
        });

        processorManager.getDatabaseHolderDefinitionList()
                .stream().filter(Objects::nonNull)
                .map(databaseObjectHolder -> {
                    if(databaseObjectHolder.databaseDefinition != null) {
                        return databaseObjectHolder.databaseDefinition.outputClassName;
                    }
                    return null;
                }).sorted(Comparator.comparing(className1 -> className1 != null ? className1.simpleName() : null))
                .forEach(it -> methodBuilder.addStatement("new $T(this)", it));

        builder.addMethod(methodBuilder.build());

        return builder.build();
    }


    /**
     * If none of the database holder databases exist, don't generate a holder.
     *
     * @return if need destory holder
     */
    public boolean isGarbage() {
        List<DatabaseObjectHolder> list = processorManager.getDatabaseHolderDefinitionList();
        for (DatabaseObjectHolder holder : list) {
            if(holder == null) {
                return true;
            }
        }
        return false;
    }
}